from typing import TypeVar, Generic, Sequence, Union, Tuple

import numpy as np
import talib

from emi.bar.data import BarData


class BarBox:
    """
    BarBo箱体。如果满的话，遵循先进先出FIFO原则，剔除最旧的数据.
    """


    def __init__(self, full_size:int):
        self.full_size = full_size
        self._bar_list: np.ndarray = np.full(full_size,BarData)
        self._open_array = np.zeros(full_size)
        self._high_array = np.zeros(full_size)
        self._low_array = np.zeros(full_size)
        self._close_array = np.zeros(full_size)
        self._volume_array = np.zeros(full_size)
        self._amount_array = np.zeros(full_size)
        self._total_count = 0

    def sub(self,start:int,end:int = 0):
        """
        取最后几个。
        param:
            start 为必须为负数
            end:（不包含）
        """
        assert start < 0 and end <= 0
        assert start != end
        sub_size = abs(end-start)
        if sub_size > self.size():
            raise RuntimeError("截取的字段长度超过父段长度")
        if sub_size == self.size():
            return self
        sub_box = BarBox(sub_size)
        sub_box._total_count = min(sub_size,self._total_count)
        if end  == 0:
            sub_box._bar_list = self._bar_list[start:]
            sub_box._open_array = self._open_array[start:]
            sub_box._high_array = self._high_array[start:]
            sub_box._low_array = self._low_array[start:]
            sub_box._close_array = self._close_array[start:]
            sub_box._volume_array = self._volume_array[start:]
            sub_box._amount_array = self._amount_array[start:]

        else:
            sub_box._bar_list = self._bar_list[start:end]
            sub_box._open_array = self._open_array[start:end]
            sub_box._high_array = self._high_array[start:end]
            sub_box._low_array = self._low_array[start:end]
            sub_box._close_array = self._close_array[start:end]
            sub_box._volume_array = self._volume_array[start:end]
            sub_box._amount_array = self._amount_array[start:end]

        return sub_box

    def is_full(self):
        return self._total_count >= self.full_size

    def tr(self):
        """
        整个时期的振幅。（值已经标准化并且*100)
        """
        return 100 * (self.high.max() - self.low.min()) / self.low.min()

    def size(self) :
        if self._total_count >= self.full_size:
            return self.full_size
        return self._total_count

    @property
    def bars(self) -> Sequence[BarData]:
        if self.is_full():
            return self._bar_list
        else:
            return self._bar_list[-self._total_count:]

    @property
    def open(self) ->np.arange:
        if self.is_full():
            return self._open_array
        else:
            return self._open_array[-self._total_count:]

    @property
    def close(self) -> np.arange:
        if self.is_full():
            return self._close_array
        else:
            return self._close_array[-self._total_count:]

    @property
    def high(self) ->np.arange:
        if self.is_full():
            return self._high_array
        else:
            return self._high_array[-self._total_count:]

    @property
    def low(self) -> np.arange:
        if self.is_full():
            return self._low_array
        else:
            return self._low_array[-self._total_count:]

    @property
    def volume(self) -> np.arange:
        if self.is_full():
            return self._volume_array
        else:
            return self._volume_array[-self._total_count:]

    @property
    def amount(self) -> np.arange:
        if self.is_full():
            return self._amount_array
        else:
            return self._amount_array[-self._total_count:]

    def clear(self):
        self._total_count = 0

    def add(self, bar:BarData):
        """
        Update new bar data into array manager.
        """
        self._bar_list[:-1] = self._bar_list[1:]
        self._bar_list[-1] = bar
        self._total_count +=1

        self._open_array[:-1] = self._open_array[1:]
        self._high_array[:-1] = self._high_array[1:]
        self._low_array[:-1] = self._low_array[1:]
        self._close_array[:-1] = self._close_array[1:]
        self._volume_array[:-1] = self._volume_array[1:]
        self._amount_array[:-1] = self._amount_array[1:]

        self._open_array[-1] = bar.open
        self._high_array[-1] = bar.high
        self._low_array[-1] = bar.low
        self._close_array[-1] = bar.close
        self._volume_array[-1] = bar.volume
        self._amount_array[-1] = bar.amount

    def extend(self,bars):
        pre_time = None
        for bar in bars:
            if pre_time:
                assert bar.datetime >= pre_time
            self.add(bar)
            pre_time = bar.datetime



    def get_turn_point(self, range_period = 4)->np.ndarray:
        """
        计算拐点。
        满足：[left_range, my, right_range]是最大或者最小值。
        0:表示非拐点
        1：表示高拐点
        -1：表示低拐点
        参数：
           range_period: 判断拐点的左右两个范围
        """

        ## 至少要含有一定数据才可以判断出来。
        assert  self.size() > range_period

        values = np.zeros(self.size())

        left_limit = - self.size()

        for i in range(left_limit, 0):
            is_left_low = True
            is_left_high = True
            bar:BarData = self.bars[i]
            high_price = bar.high
            low_price = bar.low
            if i > left_limit:
                for _left in range(max(i - range_period, left_limit), i):
                    _it_bar:BarData = self.bars[_left]
                    _it_high_price = _it_bar.high
                    _it_low_price = _it_bar.low
                    if is_left_low and _it_low_price < low_price:
                        is_left_low = False
                    if is_left_high and _it_high_price > high_price:
                        is_left_high = False
            is_right_low = True
            is_right_high = True
            if i < -1:
                for _right in range(i + 1, min(i + 1 + range_period, 0)):
                    _it_bar: BarData = self.bars[_right]
                    _it_high_price = _it_bar.high
                    _it_low_price = _it_bar.low
                    if is_right_low and _it_low_price < low_price:
                        is_right_low = False
                    if is_right_high and _it_high_price > high_price:
                        is_right_high = False

            is_low_turn = is_left_low and is_right_low
            is_high_turn = is_left_high and is_right_high

            if is_high_turn and is_low_turn:
                ### 即是高拐点、又是低拐点。（极少出现）
                values[i] =0
            if is_high_turn or is_low_turn:
                values[i] = -1 if is_low_turn else 1
        return values

    def kdj(self, fast_period=9, slow_period=3, array: bool = False) -> Union[
        Tuple[np.ndarray, np.ndarray, np.ndarray],
        Tuple[float, float, float]
    ]:
        ## 这里参数比较特殊
        assert fast_period > slow_period
        assert fast_period < 1000  # 值太多不支持
        # matype: 0=SMA, 1=EMA, 2=WMA, 3=DEMA, 4=TEMA, 5=TRIMA, 6=KAMA, 7=MAMA, 8=T3 (Default=SMA)
        # print(f"high=numpy.array({list(self.high)})")
        # print(f"low=numpy.array({list(self.high)})")
        # print(f"close=numpy.array({list(self.high)})")

        K, D = talib.STOCH(self.high, self.low, self.close, fastk_period=fast_period, slowk_matype=0,
                           slowk_period=slow_period, slowd_period=slow_period)
        if array:
            J = 3 * K - 2 * D
            return K, D, J
        return K[-1], D[-1], 3 * K[-1] - 2 * D[-1]

    def macd(self, fast_period: int = 12,slow_period: int = 26,signal_period: int = 9,array: bool = False ) \
            -> Union[Tuple[np.ndarray, np.ndarray, np.ndarray],Tuple[float, float, float]]:

        dif, dea, bar = talib.MACD(
            self.close, fast_period, slow_period, signal_period
        )
        if array:
            return dif, dea, bar
        return dif[-1], dea[-1], bar[-1]

    def arron(self,period:int = 14,array:bool=False)-> Union[Tuple[np.ndarray, np.ndarray],Tuple[float, float]]:

        aroon_down, aroon_up = talib.AROON(self.high, self.low, period)

        if array:
            return aroon_down, aroon_up
        return aroon_down[-1],aroon_up[-1]

    def ma(self,period:int,array:bool=False):
        ma_list = talib.SMA(self.close,period)
        if array:
            return ma_list
        return ma_list[-1]

    def adsoc(self,fastperiod=3, slowperiod=10,array:bool = False)-> Union[np.ndarray,float]:
        """
        函数名：ADOSC
        名称：Chaikin A/D Oscillator Chaikin震荡指标
        简介：将资金流动情况与价格行为相对比，检测市场中资金流入和流出的情况
        计算公式：fastperiod A/D - slowperiod A/D
        :param high:
        :param low:
        :param close:
        :param volume:
        :param fastperiod:
        :param slowperiod:
        :return:
        """
        adsoc = talib.ADOSC(self.high,self.low,self.close,self.volume,fastperiod,slowperiod)
        if array:
            return adsoc
        return adsoc[-1]

    def atr(self,period:int,array:bool=False)-> Union[np.ndarray,float]:
        atr = talib.ATR(self.high,self.low,self.close,period)
        if array:
            return atr
        return atr[-1]
